package com.wei.czz.framework.blog.helper;

import com.wei.czz.common.constant.EntityConstant;
import com.wei.czz.common.constant.RedisConstant;
import com.wei.czz.common.enums.ResultEnum;
import com.wei.czz.common.exception.CzzException;
import com.wei.czz.common.utils.CacheKeyUtils;
import com.wei.czz.common.utils.CopyUtils;
import com.wei.czz.common.utils.RandomNumUtils;
import com.wei.czz.framework.blog.entity.BlogEntity;
import com.wei.czz.framework.blog.service.BlogService;
import com.wei.czz.framework.common.handler.ThrottleHandler;
import com.wei.czz.framework.common.handler.redis.RedisHashHandler;
import com.wei.czz.framework.common.handler.redis.RedisHandler;
import com.wei.czz.framework.common.handler.redis.RedisStringHandler;
import lombok.AllArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wyw
 * date: 2025-01-16 20:58:00
 * className: BlogHelper
 * version: 1.0
 * description:
 */
@Component
@AllArgsConstructor
public class BlogHelper {

    private static final Logger log = LoggerFactory.getLogger(BlogHelper.class);

    private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyMMddHHmmss");

    private final BlogService blogService;

    private final RedisHashHandler redisHashHandler;

    private final RedisStringHandler redisStringHandler;

    private final RedisHandler redisHandler;

    private final ThrottleHandler throttleHandler;

    private final RedissonClient redissonClient;

    /**
     * 生成一个博客编号
     * @param userId 用户主键
     * @param blogId 博客主键
     * @return 博客编号
     */
    public String generateCode(Long userId, Long blogId) {
        /*
            博客编号生成规则：业务标识 + 时间 + 用户标识 + 博客标识 + 自增序列
            业务标识：
                值：CZZ
                长度：3位
            时间：
                值：年月日时分秒（yyMMddhhmmss）
                长度：12位
            用户标识：
                值：用户主键尾部四位数字，作为基因注入到博客编号中
                长度：4位
            博客标识：
                值：博客主键尾部两位数字，作为基因注入到博客编号中
                长度：2位
            自增序列：
                值：redis自增序列
                长度：4位
            总长度：25位
         */
        StringBuilder sb = new StringBuilder(22);
        // 1、业务标识
        sb.append("CZZ");

        String date = dateTimeFormatter.format(LocalDateTime.now());
        // 2、时间
        sb.append(date);

        // 3、用户标识
        String userIdSuffix = String.valueOf(userId % 10000);
        for (int i = userIdSuffix.length(); i < 4; i++) {
            sb.append('0');
        }
        sb.append(userIdSuffix);

        // 4、博客标识
        String blogIdSuffix = String.valueOf(blogId % 100);
        for (int i = userIdSuffix.length(); i < 2; i++) {
            sb.append('0');
        }
        sb.append(blogIdSuffix);

        /*
            获取自增值
         */
        String redisKey = RedisConstant.STRING + EntityConstant.BLOG_CODE + RedisConstant.SPLIT + date;
        Long value = redisStringHandler.increment(redisKey, 1);
        String val = value.toString();
        // 5、自增序列
        for (int i = val.length(); i < 4; i++) {
            sb.append('0');
        }
        sb.append(val);

        // 节流执行，删除自增序列
        throttleHandler.throttleRun(() -> {
            // 设置缓存键过期时间
            redisHandler.expire(redisKey, 2, TimeUnit.SECONDS);
        }, redisKey, 2, TimeUnit.SECONDS);

        return sb.toString();
    }

    /**
     * 获取博客
     * @param id 博客主键
     * @return 博客对象
     */
    public BlogEntity getBlog(Long id) {

        /*
            从缓存中获取博客
         */
        String redisKey = CacheKeyUtils.getRedisHashKey(EntityConstant.BLOG);
        String hashKey = id.toString();
        BlogEntity blog = redisHashHandler.get(redisKey, hashKey);
        if (Objects.nonNull(blog)) {
            log.info("从缓存获取到博客。{}", blog);
            return blog;
        }

        String lockKey = RedisConstant.LOCK + redisKey + RedisConstant.SPLIT + id + RedisConstant.SPLIT
                + RandomNumUtils.getRandomNum(3);
        RLock lock = redissonClient.getLock(lockKey);
        // 尝试加锁
        boolean bool = lock.tryLock();
        log.info("获取博客，尝试加锁。lockKey={} bool={}", lockKey, bool);
        if (!bool) {
            // 加锁
            lock.lock();
            log.info("获取博客，加锁成功");

            // 双重校验
            blog = redisHashHandler.get(redisKey, hashKey);
            if (Objects.nonNull(blog)) {
                log.info("double check->从缓存获取到博客。{}", blog);
                return blog;
            }
        }

        try {

            /*
                从数据库中获取博客
             */
            blog = blogService.getBlog(id);

            log.info("博客：{}", blog);

            // 缓存博客
            redisHashHandler.put(redisKey, hashKey, blog);

            return blog;
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }
    }

    public List<BlogEntity> getBlogList(List<Long> idList) {

        /*
            从缓存中获取博客
         */
        String redisKey = CacheKeyUtils.getRedisHashKey(EntityConstant.BLOG);
        List<String> hashKeyList = CopyUtils.mapList(idList, Objects::toString);
        List<BlogEntity> blogList = redisHashHandler.multiGet(redisKey, hashKeyList);
        if (!CollectionUtils.isEmpty(blogList) && blogList.size() == idList.size()) {
            log.info("从缓存获取到博客列表。{}", blogList);
            return blogList;
        }

        String lockKey = RedisConstant.LOCK + redisKey + RedisConstant.SPLIT + RandomNumUtils.getRandomNum(3);
        RLock lock = redissonClient.getLock(lockKey);
        // 尝试加锁
        boolean bool = lock.tryLock();
        log.info("获取博客列表，尝试加锁。lockKey={} bool={}", lockKey, bool);
        if (!bool) {
            // 加锁
            lock.lock();
            log.info("获取博客列表，加锁成功");

            // 双重校验
            blogList = redisHashHandler.multiGet(redisKey, hashKeyList);
            if (!CollectionUtils.isEmpty(blogList) && blogList.size() == idList.size()) {
                log.info("double check->从缓存获取到博客列表。{}", blogList);
                return blogList;
            }
        }
        if (Objects.isNull(blogList)) {
            blogList = new ArrayList<>();
        }

        try {

            // 构造博客映射
            Map<Long, BlogEntity> blogMap = CopyUtils.listToMap(blogList, BlogEntity::getId, Function.identity());
            List<Long> notInIdList = new ArrayList<>();
            for (Long id : idList) {
                if (!blogMap.containsKey(id)) {
                    notInIdList.add(id);
                }
            }

            /*
                获取博客
             */
            List<BlogEntity> dbBlogList = blogService.getBlogList(notInIdList);
            for (BlogEntity blog : dbBlogList) {
                blogMap.put(blog.getId(), blog);
            }

            blogList = new ArrayList<>();
            for (Long id : idList) {
                BlogEntity blog = blogMap.get(id);
                if (Objects.isNull(blog)) {
                    log.error("博客不存在。id={}", id);
                    throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(), "博客不存在");
                }
                blogList.add(blog);
            }

            return blogList;

        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }
    }
}
